﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using JCFramework.Attributes;

namespace JCFramework.Extensions
{
    public static class AttachDataExtensions
    {

        private static Dictionary<ICustomAttributeProvider, Dictionary<object, object>> s_mapAttributeProviderKeyValue =
            new Dictionary<ICustomAttributeProvider, Dictionary<object, object>>();

        public static object GetAttachedData(this ICustomAttributeProvider provider, object key)
        {
            if (!s_mapAttributeProviderKeyValue.ContainsKey(provider)
                || !s_mapAttributeProviderKeyValue[provider].ContainsKey(key))
            {
                var attributes = (AttachDataAttribute[])provider.GetCustomAttributes(typeof(AttachDataAttribute), false);
                var value = attributes.First(a => a.Key.Equals(key)).Value;
                if (!s_mapAttributeProviderKeyValue.ContainsKey(provider))
                {
                    s_mapAttributeProviderKeyValue.Add(provider, new Dictionary<object, object>());
                }
               
                s_mapAttributeProviderKeyValue[provider].Add(key, value);
            }

            return s_mapAttributeProviderKeyValue[provider][key];
        }

        public static T GetAttachedData<T>(this ICustomAttributeProvider provider, object key)
        {
            return (T)provider.GetAttachedData(key);
        }

        public static object GetAttachedData(this Enum value, object key)
        {
            return value.GetType().GetField(value.ToString()).GetAttachedData(key);
        }

        public static T GetAttachedData<T>(this Enum value, object key)
        {
            return (T)value.GetAttachedData(key);
        }

        private static T CreateAttachedInstance<T>(ICustomAttributeProvider provider)
        {
            var type = typeof(T);
            T t = (T)Activator.CreateInstance(type);
            var attributes = (AttachDataAttribute[])provider.GetCustomAttributes(
                typeof(AttachDataAttribute), false);

            foreach (var attribute in attributes)
            {
                var property = type.GetProperty(attribute.Key.ToString(),
                    BindingFlags.SetField | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
                if (property != null)
                {
                    property.SetValue(t, attribute.Value, null);
                }
            }

            return t;
        }

        private static Dictionary<ICustomAttributeProvider, object> s_dictProviderInstanceMapping =
            new Dictionary<ICustomAttributeProvider, object>();

        private static object s_attachedInstanceMutex = new object();

        public static T GetAttachedInstance<T>(this ICustomAttributeProvider provider)
        {
            if (!s_dictProviderInstanceMapping.ContainsKey(provider))
            {
                lock (s_attachedInstanceMutex)
                {
                    if (!s_dictProviderInstanceMapping.ContainsKey(provider))
                    {
                        s_dictProviderInstanceMapping[provider] = CreateAttachedInstance<T>(provider);
                    }
                }
            }

            return (T)s_dictProviderInstanceMapping[provider];
        }

        public static T GetAttachedInstance<T>(this Enum value)
        {
            return value.GetType().GetField(value.ToString()).GetAttachedInstance<T>();
        }
    }
}
